package cn.bj.brook.tkit;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author zhaoych
 * 周期日程工具，专门用来拆单日程，判定日程是否冲突
 */
public class PeriodScheduleToolKit {

    private static final Pattern datePattern = Pattern.compile("[1,2]\\d{3}-(0\\d|1[0-2])-([0-2]\\d|3[0,1])([ ])(0\\d{1}|1\\d{1}|2[0-3]):([0-5]\\d{1})");

    /**
     * 将以 YYYY-MM-DD 格式传入的参数作为开始时间，后面参数作为的范围，返回一个日期的拆单数组，格式和beginDate相同<br></br>
     * 截止到endDate为最终日的时间，且包含开始日期和结束日志 <br><br/>
     * 比如 2022-06-12 开始，每周的周三，到2024-05-01结束 <br><br/>
     *
     * @param beginTime     日程开始日期 形如：YYYY-MM-DD HH:mm<br><br/>
     * @param endTime       日程结束日期 形如：YYYY-MM-DD HH:mm<br><br/>
     * @param period        周期间隔的数量，每3天，每2月，每1年，一般都会传1<br><br/>
     * @param days          选中重复的日期，比如每周的周1，周3；每月的23日，27日；如果是29,30,31日要对2月做特殊处理；如果是31日对30日做特殊处理br><br/>
     * @param unit          间隔的单位，天，周，月，年，重复的日期会根据年来生效；选中年的话，就默认每年今天br><br/>
     * @param repeatEndDate 周期的终止日期br><br/>
     * @return 拆分的日期数组，每个元素都是YYYY-MM-DD HH:mm格式
     */
    public static String[][] parse(String beginTime, String endTime, int period, int[] days, int unit, String repeatEndDate) {
        if (!validateDateFormat(beginTime)) {
            throw new RuntimeException("不合法的日期格式");
        }
        if (!validateDateFormat(endTime)) {
            throw new RuntimeException("不合法的日期格式");
        }
        if (!validateDateFormat(repeatEndDate)) {
            throw new RuntimeException("不合法的日期格式");
        }
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
        Calendar startTimeCalendar = acquireCalendar(simpleDateFormat, beginTime);
        if (startTimeCalendar == null) {
            // 未获取成功开始日期对象
            throw new RuntimeException("不合法的日期格式");
        }
        Calendar endTimeCalendar = acquireCalendar(simpleDateFormat, endTime);
        if (endTimeCalendar == null) {
            // 未获取成功开始日期对象
            throw new RuntimeException("不合法的日期格式");
        }
        Calendar repeatEndCalendar = acquireCalendar(simpleDateFormat, repeatEndDate);
        if (repeatEndCalendar == null) {
            // 未获取成功结束日期对象
            throw new RuntimeException("不合法的日期格式");
        }
        List<String[]> result = new ArrayList<>();
        // 按照需求拆分日期，按月，按周，按天
        switch (unit) {
            case Calendar.DATE:
                result = parseByDay(simpleDateFormat, startTimeCalendar, endTimeCalendar, repeatEndCalendar, period);
                break;
            case Calendar.DAY_OF_WEEK:
                result = parseByWeek(simpleDateFormat, startTimeCalendar, endTimeCalendar, repeatEndCalendar, period, days);
                break;
            case Calendar.MONTH:
                result = parseByMonth(simpleDateFormat, startTimeCalendar, endTimeCalendar, repeatEndCalendar, period, days);
                break;
            default:
                break;
        }
        return result.toArray(new String[][]{});
    }

    /**
     * 辅助计算函数主要用于计算按照每月的第几日，因为每个月的30日，31日不一样，还要考虑闰年问题
     *
     * @param startTimeCalendar 日程开始日期
     * @param endTimeCalendar   日程结束日期
     * @param targetDate        要匹配的目标日期
     * @param simpleDateFormat  sdf对象，用来格式化
     * @param result            需要将计算的结果加入的列表
     */
    private static Boolean auxCalculate(Calendar startTimeCalendar, Calendar endTimeCalendar, Calendar repeatEndCalendar, int targetDate, SimpleDateFormat simpleDateFormat, List<String[]> result) {
        Calendar startClone = (Calendar) startTimeCalendar.clone();
        Calendar endClone = (Calendar) endTimeCalendar.clone();
        // 考虑日期
        int month = startClone.get(Calendar.MONTH) + 1;
        int tempDate = targetDate;
        switch (month) {
            case 4:
            case 6:
            case 9:
            case 11:
                if (targetDate > 30) {
                    tempDate = 30;
                }
                break;
            case 2:
                if (isLeapYear(startTimeCalendar) && targetDate > 29) {
                    tempDate = 29;
                }
                if (!isLeapYear(startTimeCalendar) && targetDate > 28) {
                    tempDate = 28;
                }
                break;
            default:
                break;

        }
        startClone.set(Calendar.DAY_OF_MONTH, tempDate);
        endClone.set(Calendar.DAY_OF_MONTH, tempDate);

        // 获取本月实际天数
        int maximum = startClone.getActualMaximum(Calendar.DAY_OF_MONTH);
        int currentDay = startClone.get(Calendar.DAY_OF_MONTH);

        if (currentDay <= maximum) {
            if (repeatEndCalendar.after(endClone) || repeatEndCalendar.equals(endClone)) {
                fromCalendar(simpleDateFormat, startClone, endClone, result);
            }
            // 判断当天是不是本月的最后一天 如果是最后一天 结束循环
            return currentDay == maximum;
        }
        return true;
    }

    /**
     * 按照每N个月的第L日，第K日找到匹配的日期记录
     *
     * @param simpleDateFormat  sdf对象，用来格式化
     * @param startTimeCalendar 日程开始日期
     * @param endTimeCalendar   日程结束日期
     * @param repeatEndCalendar 重复终止日期
     * @param period            月的间隔，为N参数
     * @param days              月里面的L日，K日
     * @return 包含匹配结果的数组
     */
    private static List<String[]> parseByMonth(SimpleDateFormat simpleDateFormat, Calendar startTimeCalendar, Calendar endTimeCalendar, Calendar repeatEndCalendar, int period, int[] days) {
        // 这个关键是要处理月末信息，是否包含31号，闰年的2月
        List<String[]> result = new LinkedList<>();

        // 处理当月 - 如果不需要处理当月，可以注释掉此部分代码
        int dayOfMonth = startTimeCalendar.get(Calendar.DAY_OF_MONTH);
        // 处理只选开始日期，选择月重复,不选择每月几号
        if (days == null || days.length == 0) {
            days = new int[]{dayOfMonth};
        }
        for (int targetDate : days) {
            if (dayOfMonth <= targetDate) {
                Boolean isLastDay = auxCalculate(startTimeCalendar, endTimeCalendar, repeatEndCalendar, targetDate, simpleDateFormat, result);
                if (isLastDay) {
                    break;
                }
            }
        }

        // 日期回拨到本月1日，按月加
        startTimeCalendar.set(Calendar.DAY_OF_MONTH, 1);
        startTimeCalendar.add(Calendar.MONTH, period);
        endTimeCalendar.set(Calendar.DAY_OF_MONTH, 1);
        endTimeCalendar.add(Calendar.MONTH, period);
        while (startTimeCalendar.before(repeatEndCalendar) || startTimeCalendar.equals(repeatEndCalendar)) {
            for (int targetDate : days) {
                Boolean isLastDay = auxCalculate(startTimeCalendar, endTimeCalendar, repeatEndCalendar, targetDate, simpleDateFormat, result);
                if (isLastDay) {
                    break;
                }
            }
            startTimeCalendar.add(Calendar.MONTH, period);
            endTimeCalendar.add(Calendar.MONTH, period);
        }
        return result;
    }

    /**
     * 判断年是否为闰年，判断方案为将当前年的日期设定为2月28日，然后加1，如果出现29日则是闰年，否则不是
     *
     * @param calendar 一个被传入的日期，判断日期所属的年
     * @return 是否是闰年
     */
    private static boolean isLeapYear(Calendar calendar) {
        int year = calendar.get(Calendar.YEAR);
        Calendar leap = Calendar.getInstance();
        leap.set(Calendar.YEAR, year);
        leap.set(Calendar.MONTH, 1);
        leap.set(Calendar.DAY_OF_MONTH, 28);
        leap.add(Calendar.DAY_OF_MONTH, 1);
        return leap.get(Calendar.DAY_OF_MONTH) == 29;
    }

    private static List<String[]> parseByWeek(SimpleDateFormat simpleDateFormat, Calendar startTimeCalendar, Calendar endTimeCalendar, Calendar repeatEndCalendar, int period, int[] days) {
        List<String[]> result = new LinkedList<>();
        // 处理当前所在周有无可能包含需要的星期几
        // 然后将指针移动到下一周的开始
        // 在这个周中寻找合适的星期几把日期记录下来
        // 再加X周
        // 直到超过结束日期
        // 处理当周 - 如果不需要处理当周，可以注释掉此部分代码
        int dayOfWeek = startTimeCalendar.get(Calendar.DAY_OF_WEEK);
        // 处理只选开始日期，选择周重复,不选择周几
        if (days == null || days.length == 0) {
            days = new int[]{dayOfWeek};
        }
        // 第0天是星期日，千万别记错了
        for (int targetWeekDay : days) {
            if (dayOfWeek <= targetWeekDay) {
                Calendar startClone = (Calendar) startTimeCalendar.clone();
                Calendar endClone = (Calendar) endTimeCalendar.clone();
                startClone.set(Calendar.DAY_OF_WEEK, targetWeekDay);
                endClone.set(Calendar.DAY_OF_WEEK, targetWeekDay);
                fromCalendar(simpleDateFormat, startClone, endClone, result);
            }
        }
        // 将日期挪动到上周日（循环开始）
        int diff = Calendar.SUNDAY - dayOfWeek;
        startTimeCalendar.add(Calendar.DAY_OF_MONTH, diff);
        startTimeCalendar.add(Calendar.WEEK_OF_YEAR, period);
        endTimeCalendar.add(Calendar.DAY_OF_MONTH, diff);
        endTimeCalendar.add(Calendar.WEEK_OF_YEAR, period);

        while (startTimeCalendar.before(repeatEndCalendar) || startTimeCalendar.equals(repeatEndCalendar)) {
            for (int targetWeekDay : days) {
                diff = targetWeekDay - startTimeCalendar.get(Calendar.DAY_OF_WEEK);
                Calendar startClone = (Calendar) startTimeCalendar.clone();
                startClone.add(Calendar.DAY_OF_MONTH, diff);
                diff = targetWeekDay - endTimeCalendar.get(Calendar.DAY_OF_WEEK);
                Calendar endClone = (Calendar) endTimeCalendar.clone();
                endClone.add(Calendar.DAY_OF_MONTH, diff);
                if (repeatEndCalendar.after(endClone) || repeatEndCalendar.equals(endClone)) {
                    fromCalendar(simpleDateFormat, startClone, endClone, result);
                }
            }
            startTimeCalendar.add(Calendar.WEEK_OF_YEAR, period);
            endTimeCalendar.add(Calendar.WEEK_OF_YEAR, period);
        }
        return result;
    }

    /**
     * 将当前日历对象返回成为一个YYYY-MM-DD格式的日期字符串
     *
     * @param simpleDateFormat sdf对象，用来格式化
     * @param calendar         要被转换的日历对象
     * @return 格式YYYY-MM-DD字符串
     */
    private static String fromCalendar(SimpleDateFormat simpleDateFormat, Calendar calendar) {
        Date date = calendar.getTime();
        //noinspection AlibabaAvoidCallStaticSimpleDateFormat
        String formatDate = simpleDateFormat.format(date);
        System.out.println("当前产生的日期是:" + formatDate);
        return formatDate;
    }

    private static void fromCalendar(SimpleDateFormat simpleDateFormat, Calendar startTimeCalendar, Calendar endTimeCalendar, List<String[]> result) {
        String[] one = new String[]{fromCalendar(simpleDateFormat, startTimeCalendar), fromCalendar(simpleDateFormat, endTimeCalendar)};
        result.add(one);
    }

    private static List<String[]> parseByDay(SimpleDateFormat simpleDateFormat, Calendar startTimeCalendar, Calendar endTimeCalendar, Calendar repeatEndCalendar, int period) {
        List<String[]> result = new ArrayList<>();
        while (startTimeCalendar.before(repeatEndCalendar) || startTimeCalendar.equals(repeatEndCalendar)) {
            fromCalendar(simpleDateFormat, startTimeCalendar, endTimeCalendar, result);
            startTimeCalendar.add(Calendar.DAY_OF_MONTH, period);
            endTimeCalendar.add(Calendar.DAY_OF_MONTH, period);
        }
        return result;
    }

    /**
     * 验证日期格式是否为YYYY-MM-DD格式
     *
     * @param date 要被验证的日期输入字符串
     * @return 是否匹配
     */
    private static boolean validateDateFormat(String date) {
        Matcher matcher = datePattern.matcher(date);
        return matcher.matches();
    }

    /**
     * 根据传入的格式化对象获取一个新的日历对象
     *
     * @param simpleDateFormat 传入的sdf对象
     * @param inputDateString  传入的日期参数
     * @return 一个新的Calendar对象
     */
    private static Calendar acquireCalendar(SimpleDateFormat simpleDateFormat, String inputDateString) {
        Date date;
        try {
            date = simpleDateFormat.parse(inputDateString);
        } catch (ParseException e) {
            e.printStackTrace();
            return null;
        }
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        return calendar;
    }

    /**
     * 判断周期是否冲突的代码
     *
     * @param sourceDate 列表，每个列表的元素都是一个二位数组，元素0是开始时间，元素1是结束时间
     * @param targetDate 需要被对比的日程的开始时间
     * @return 当targetDate落入到 sourceDate的区间，则返回targetDate的列表
     */
    public static String[][] conflictDateTime(List<String[]> sourceDate, List<String[]> targetDate) {
        List<String[]> result = new LinkedList<>();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
        for (String[] source : sourceDate) {
            String start = source[0];
            String end = source[1];
            Calendar sourceStartCalendar = acquireCalendar(simpleDateFormat, start);
            Calendar sourceEndCalendar = acquireCalendar(simpleDateFormat, end);
            for (String[] target : targetDate) {
                //开始日期在区间内
                Calendar targetStartCalendar = acquireCalendar(simpleDateFormat, target[0]);
                boolean isHeadRepeat = Objects.requireNonNull(targetStartCalendar).after(sourceStartCalendar) || targetStartCalendar.equals(sourceStartCalendar);
                boolean isTailRepeat = targetStartCalendar.before(sourceEndCalendar) || targetStartCalendar.equals(sourceEndCalendar);

                //结束日期在区间内
                Calendar targetEndCalendar = acquireCalendar(simpleDateFormat, target[1]);
                boolean isHeadRepeat1 = Objects.requireNonNull(targetEndCalendar).after(sourceStartCalendar) || targetEndCalendar.equals(sourceStartCalendar);
                boolean isTailRepeat1 = targetEndCalendar.before(sourceEndCalendar) || targetEndCalendar.equals(sourceEndCalendar);

                //目标日期区间包含源日期区间
                boolean isHeadRepeat2 = Objects.requireNonNull(targetStartCalendar).before(sourceStartCalendar) || targetEndCalendar.equals(sourceStartCalendar);
                boolean isTailRepeat2 = targetEndCalendar.after(sourceEndCalendar) || targetEndCalendar.equals(sourceEndCalendar);

                if ((isHeadRepeat && isTailRepeat)||(isHeadRepeat1 && isTailRepeat1)||(isHeadRepeat2 && isTailRepeat2)) {
                    result.add(target);
                }
            }
        }
        return result.toArray(new String[][]{});
    }

    /**
     * 测试主函数
     *
     * @param args 输入的程序参数
     */
    public static void main(String[] args) {
        String testBeginDate = "2022-06-01 10:00";
        String testEndDate = "2022-06-01 11:00";
        String repeatEndDate = "2022-07-30 23:59";
        boolean isValidDate = validateDateFormat(testBeginDate);
        System.out.println(isValidDate);
        // 每X天
        String[][] result = parse(testBeginDate, testEndDate, 2, null, Calendar.DATE, repeatEndDate);
        for (String[] rs : Objects.requireNonNull(result)) {
            System.out.print(rs[0] + "至" + rs[1]);
            System.out.print(",");
        }
        System.out.println();

        // 每X周的周A、周B
        testBeginDate = "2022-06-09 10:00";
        testEndDate = "2022-06-09 23:59";
        repeatEndDate = "2022-07-07 23:59";

        String[][] result1 = parse(testBeginDate, testEndDate, 2, new int[]{}, Calendar.DAY_OF_WEEK, repeatEndDate);
        for (String[] rs : Objects.requireNonNull(result1)) {
            System.out.print(rs[0] + "至" + rs[1]);
            System.out.print(",");
        }
        System.out.println();

        // 每x月的30,31
        testBeginDate = "2022-02-12 11:00";
        testEndDate = "2022-02-12 12:00";
        repeatEndDate = "2022-12-31 23:59";
        String[][] result2 = parse(testBeginDate, testEndDate, 2, new int[]{28, 29, 30, 31}, Calendar.MONTH, repeatEndDate);
        for (String[] rs : Objects.requireNonNull(result2)) {
            System.out.print(rs[0] + "至" + rs[1]);
            System.out.print(",");
        }
        System.out.println();

        //获取冲突日期
        String[][] result3 = conflictDateTime(Arrays.asList(result), Arrays.asList(result1));
        for (String[] rs : Objects.requireNonNull(result3)) {
            System.out.print(rs[0] + "至" + rs[1]);
            System.out.print(",");
        }
        System.out.println();
    }
}
